﻿using Bilibili.Common;
using Bilibili.Common.Api;
using Bilibili.Common.Database;
using Bilibili.Common.Log;
using Bilibili.Model.Common;
using Bilibili.Model.Video;
using Bilibili.WebClient.Common;
using PCLUntils.Json;
using PCLUntils.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bilibili.WebClient.Api
{
    public abstract class PlayUrlClient : ClientBase
    {
        public DanmakuClient Client { get; }
        private UserClient UserManager { get; }
        private string USER_AGENT => Current.PLAY_USER_AGENT;
        public PlayUrlClient()
        {
            Client = new DanmakuClient();
            UserManager = new UserClient();
        }
        public async Task<string> GetBiliBili(long cid, int segmentIndex) => await Client.GetBiliBili(cid, segmentIndex);
        public async Task<ReturnPlayModel> LoadVideo(PlayerModel pageModel, int qn, long uid)
        {
            try
            {
                if (pageModel != null)
                {
                    ReturnPlayModel model = default;
                    var danmakus = await Client.GetBiliBili(pageModel.Mid);
                    switch (pageModel.Mode)
                    {
                        case PlayMode.Movie:
                        case PlayMode.Bangumi:
                        case PlayMode.VipBangumi:
                            model = await GetBangumiUrl(pageModel, qn);
                            break;
                        case PlayMode.Video:
                            model = await GetVideoUrl(pageModel.Aid, pageModel.Mid, uid, qn);
                            break;
                        case PlayMode.Local:
                        case PlayMode.FormLocal:
                            break;
                    }
                    if (model != null)
                        model.DamakuXml = danmakus;
                    return model ?? BuildPlayFailedResult("获取播放信息失败！");
                }
                else
                    return BuildPlayFailedResult("资源信息为空！");
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("LoadVideo", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetVideoUrl(string aid, long cid, long uid, int qn)
        {
            try
            {
                if (qn < 10)
                {
                    qn = qn switch
                    {
                        1 => 32,
                        2 => 64,
                        3 => 80,
                        4 => 116,
                        _ => 64,
                    };
                }
                if (SettingHelper.UseDASH)
                {
                    var bilidash = await GetVideoUrlDASH(aid, cid, uid, qn);
                    if (bilidash != null)
                        return bilidash;
                }
                var biliv1 = await GetVideoUrlV1(aid, cid, uid, qn);
                if (biliv1.IsSuccess())
                    return biliv1;
                var biliplus = await GetBiliPlusUrl(aid, cid, qn, 0);
                return biliplus;
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetVideoUrl", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetVideoUrlDASH(string aid, long cid, long uid, int qn)
        {
            try
            {
                var apiInfo = new ApiInfo("https://api.bilibili.com/x/player/playurl", $"avid={aid}&cid={cid}&qn={qn}&type=&otype=json&fourk=1&fnver=0&fnval=4048&mid={uid}", true, ApiProviderBase.Build6);
                string re = await ApiClient.GetResults(apiInfo);
                var headers = new DataSourceHeader(Current.HOST, USER_AGENT);
                return AnalyzeDashResources(re, qn, headers);
            }
            catch (Exception ex)
            {
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetVideoUrlV1(string aid, long cid, long uid, int qn)
        {
            try
            {
                var apiInfo = new ApiInfo("https://api.bilibili.com/x/player/playurl", $"avid={aid}&cid={cid}&qn={qn}&type=&otype=json&mid={uid}", true, ApiProviderBase.Build6);
                string re = await ApiClient.GetResults(apiInfo);
                var m = re.ParseObject<FlvPlayerUrlModel>();
                if (m.Code == 0)
                {
                    var urls = new List<string>();
                    if (m.Data.Durl != null && m.Data.Durl.Count > 0)
                        urls.AddRange(m.Data.Durl.Select(x => x.Url));
                    return BuildPlaySuccessResult(urls, qn, $"https://www.bilibili.com/video/av{aid}/", USER_AGENT);
                }
                else
                    return BuildPlayFailedResult(m.Message);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetVideoUrlV1", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetBiliPlusUrl(string aid, long cid, int qn, int season_type)
        {
            try
            {
                var url = new StringBuilder($"https://www.biliplus.com/BPplayurl.php?appkey={ApiProvider.Instance.WebVideoKey.Appkey}&cid={cid}&qn={qn}&type=&otype=json&module=bangumi&season_type={season_type}");
                if (ApiProvider.Instance.IsLogin)
                    url.Append($"&access_key={ApiProvider.Instance.AccessKey}&mid={ApiProvider.Instance.CurrentUserId}");
                if (SettingHelper.UseDASH)
                    url.Append("&fnver=0&fnval=16");
                var header = new Dictionary<string, string>();
                string re = await ApiClient.GetResults(GetSignUrl(url.ToString(), ApiProvider.Instance.WebVideoKey));
                var m = re.ParseObject<FlvPlayerUrlModel>();
                if (m.Code == 0)
                {
                    List<string> urls = new List<string>();
                    foreach (var item in m.Data.Durl)
                        urls.Add(item.Url);
                    var referer = $"https://www.bilibili.com/video/av{aid}/";
                    return BuildPlaySuccessResult(urls, qn, referer, USER_AGENT);
                }
                else
                    return BuildPlayFailedResult(m.Message);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBiliPlusUrl", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        public async Task<ReturnPlayModel> GetBangumiUrl(PlayerModel model, int qn)
        {
            try
            {
                if (qn < 10)
                {
                    qn = qn switch
                    {
                        1 => 32,
                        2 => 64,
                        3 => 80,
                        4 => 116,
                        _ => 64,
                    };
                }
                ReturnPlayModel result = null;
                if (SettingHelper.UseDASH)
                    result = await GetBilibiliBangumiUrlDash(model, qn);
                if (!result.IsSuccess())
                    result = await GetBilibiliBangumiUrl(model, qn);
                if (!result.IsSuccess())
                    result = await Get23MoeUrl(model, qn);
                if (!result.IsSuccess())
                    result = await GetBiliPlus(model, qn);
                if (result != null)
                    result.DamakuXml = await Client.GetBiliBili(model.Mid);
                return result;
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBangumiUrl", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetBilibiliBangumiUrlDash(PlayerModel model, int qn)
        {
            try
            {
                var urls = new List<string>();
                var apiInfo = new ApiInfo("https://api.bilibili.com/pgc/player/web/playurl", $"cid={model.Mid}&otype=json&type=&module=bangumi&season_type={model.Season_Type}&qn={qn}&fourk=1&fnver=0&fnval=4048",
                  true, ApiProviderBase.Build6);
                var re = await ApiClient.GetResultsUTF8Encode(apiInfo);
                var headers = new DataSourceHeader(Current.HOST, USER_AGENT);
                return AnalyzeDashResources(re, qn, headers);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBilibiliBangumiUrlDash", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetBilibiliBangumiUrl(PlayerModel model, int qn)
        {
            try
            {
                var apiInfo = new ApiInfo("https://api.bilibili.com/pgc/player/web/playurl",
                    $"cid={model.Mid}&otype=json&type=&module=bangumi&season_type={model.Season_Type}&qn={qn}", true, ApiProviderBase.Build6);
                var re = await ApiClient.GetResultsUTF8Encode(apiInfo);
                var m = re.ParseObject<FlvPlayerUrlModel>();
                if (m.Code == 0 && !re.Contains("8986943"))
                {
                    var urls = new List<string>();
                    if (m.Data.Durl != null && m.Data.Durl.Count > 0)
                        urls.AddRange(m.Data.Durl.Select(x => x.Url));
                    return BuildPlaySuccessResult(urls, qn, Current.HOST, USER_AGENT);
                }
                else
                {
                    apiInfo = new ApiInfo("https://api.bilibili.com/pgc/player/web/playurl", $"&cid={model.Mid}&qn={qn}&type=&otype=json&module=bangumi&season_type={model.Season_Type}", true);
                    var result = await ApiClient.GetResultsUTF8Encode(apiInfo);
                    m = result.Replace("result", "data").ParseObject<FlvPlayerUrlModel>();
                    var urls = new List<string>();
                    if (m.Data.Durl != null && m.Data.Durl.Count > 0)
                        urls.AddRange(m.Data.Durl.Select(x => x.Url));
                    return BuildPlaySuccessResult(urls, qn, Current.HOST, USER_AGENT);
                }
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBilibiliBangumiUrl", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> Get23MoeUrl(PlayerModel model, int qn)
        {
            try
            {
                var urls = new List<string>();
                var re3 = await ApiClient.GetResults(string.Format("https://moe.nsapps.cn/api/v1/BiliAnimeUrl?animeid={0}&cid={1}&epid={2}&rnd={3}", model.BanId, model.Mid, model.BanInfo.Episode_Id, ApiProvider.Instance.TimeSpanSeconds));
                var obj = re3.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var header = new DataSourceHeader(Current.HOST, USER_AGENT);
                    if (obj["mode"].ToString() == "mp4")
                    {
                        urls.Add(obj["data"][0]["url"].ToString());
                        return BuildPlaySuccessResult(urls, qn, header);
                    }
                    else
                        return BuildPlayFailedResult();
                }
                else
                    return BuildPlayFailedResult(obj["message"].ToString());
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("Get23MoeUrl", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetBiliPlus(PlayerModel model, int qn)
        {
            try
            {
                var biliplus = await GetBiliPlusUrl(model.Mid.ToString(), model.Episode_Id.ToInt64(), qn, model.Season_Type);
                if (biliplus != null)
                    return biliplus;
                biliplus = await GetBiliPlusUrl2(model, qn);
                return biliplus;
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBiliPlus", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        private async Task<ReturnPlayModel> GetBiliPlusUrl2(PlayerModel model, int qn)
        {
            try
            {
                var urls = new List<string>();
                var re = await ApiClient.GetResultsUTF8Encode($"https://www.biliplus.com//BPplayurl.php?cid={model.Mid}&otype=json");
                var obj = re.ToJObject();
                if (obj["code"].ToInt32() == 10004)
                {
                    if (ApiProvider.Instance.IsLogin)
                    {
                        var _cookie = await UserManager.AuthBiliPlus();
                        if (!string.IsNullOrEmpty(_cookie))
                            return await GetBiliPlusUrl2(model, qn);
                        else
                            return BuildPlayFailedResult("获取授权信息失败！");
                    }
                    else
                        return BuildPlayFailedResult(obj["message"].ToString());
                }
                if (obj["code"].ToInt32() == 0 && !re.Contains("8986943"))
                {
                    foreach (var item in obj["durl"])
                        urls.Add(item["url"].ToString());
                    return BuildPlaySuccessResult(urls, qn, "https://www.bilibili.com/bangumi/play/ep" + model.Episode_Id, USER_AGENT);
                }
                else
                    return BuildPlayFailedResult(obj["message"].ToString());
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetBiliPlusUrl2", ex);
                return BuildPlayFailedResult(ex.Message);
            }
        }
        protected abstract ReturnPlayModel AnalyzeDashResources(string re, int qn, DataSourceHeader headers);
    }
}